<template>
  <div class="geo-chart">
    <main ref="main"></main>
    <section ref="tooltip"></section>
  </div>
</template>
<script lang="ts">
import * as d3 from "d3";
import { NumberValue } from "d3";
import { Vue, Component, Ref } from "vue-property-decorator";
type D3Selection = d3.Selection<any, any, any, any>;
type D3LinearScale = d3.ScaleLinear<any, any, any>;
@Component({
  name: "GeoChart",
  components: {}
})
export default class GeoChart extends Vue {
  private config!: GeoChartConfig;
  private w!: number;
  private h!: number;
  private startX!: number;
  private startY!: number;
  private endX!: number;
  private endY!: number;

  private block!: number;

  private maxYear!: number;
  private maxDepth!: number;

  private svg!: D3Selection;
  private zoom!: D3Selection;
  private lend!: D3Selection;
  private defs!: D3Selection;
  private temp!: D3Selection;
  private series!: D3Selection;

  private xscale!: D3LinearScale;
  private yscale!: D3LinearScale;
  private tscale!: D3LinearScale;
  private taxis!: d3.Axis<NumberValue>;

  @Ref("main")
  private $main!: HTMLDivElement;

  public setOptions(config: GeoChartConfig) {
    this.config = config;

    this.initParams();
    this.addSvg();
    this.addStyle();
    this.addTempAxis();
    this.addTempDashLine();
    this.addDepthAxis();
    this.addYearAxis();
    this.addBlocks();
    this.addSeryLines();
  }

  private initParams() {
    this.block = 35;

    const rect = this.$main.getBoundingClientRect();
    this.w = rect.width;
    this.h = rect.height;

    this.startX = (this.config.grid?.left || 0) + 140;
    this.endX = this.w - (this.config.grid?.right || 0) - this.block;
    this.startY = (this.config.grid?.top || 0) + this.block;
    this.endY = this.h - (this.config.grid?.bottom || 0) - 50;
  }

  private addSvg() {
    this.svg = d3
      .select(this.$main)
      .append("svg")
      .attr("width", "100%")
      .attr("height", "100%");

    this.defs = this.svg.append("defs");
    this.zoom = this.svg.append("g").classed("zoom", true);
    this.lend = this.svg.append("g").classed("lend", true);
  }

  private addStyle() {
    this.defs.append("style").text(`
      .temp-axis text{
        fill: ${this.config.axies.temp.color}
      }
      .temp-axis line, .temp-axis path{
        stroke: ${this.config.axies.temp.color}
      }
      .depth-axis text{
        fill: ${this.config.axies.depth.color}
      }
      .depth-axis line, .depth-axis path{
        stroke: ${this.config.axies.depth.color}
      }
      .year-axis text{
        fill: ${this.config.axies.year.color}
      }
      .year-axis line, .year-axis path{
        stroke: ${this.config.axies.year.color}
      }
      .block-item{
        fill: none;
        stroke: ${this.config.series.color || "#000"};
      }
      .block-item text{
        text-anchor: middle;
        dominant-baseline: middle;
        fill: ${this.config.series.color || "#000"};
        font-size: ${this.config.series.fontSize || 14}px;
        font-weight: ${this.config.series.fontWeight || "bold"};
        stroke: none;
      }
      .circle-point{
        cursor: pointer;
      }
      .circle-point:hover{
        opacity: 0.7;
      }
    `);
  }

  private addTempAxis() {
    this.temp = this.zoom
      .append("g")
      .classed("temp-axis", true)
      .attr(
        "transform",
        `translate(${60 + (this.config.grid?.left || 0)}, ${0})`
      );

    const domain = [this.config.axies.temp.end, this.config.axies.temp.start];

    this.tscale = d3
      .scaleLinear()
      .range([this.endY, this.startY])
      .domain(domain)
      .nice();

    this.taxis = d3
      .axisLeft(this.tscale)
      .ticks(Math.floor(Math.abs((domain[1] - domain[0]) / 10)));

    this.temp.append("g").call(this.taxis);
    this.temp
      .append("text")
      .text(this.config.axies.temp.label)
      .attr("transform", `translate(0, ${(this.startY + this.endY) / 2})`)
      .attr("x", -40)
      .style("fill", this.config.axies.temp.color || "#000")
      .style("font-size", this.config.axies.fontSize || 14)
      .style("font-weight", this.config.axies.fontWeight || "bold")
      .style("writing-mode", "tb")
      .style("text-anchor", "middle")
      .style("dominant-baseline", "middle");
  }

  private addTempDashLine() {
    const domain = this.tscale.domain();
    const target: number[] = [],
      start = d3.min(domain) as number,
      end = d3.max(domain) as number;
    let curr = start;
    while (curr <= end) {
      target.push(curr);
      curr += 10;
    }

    this.temp
      .append("g")
      .classed("temp-lines", true)
      .selectAll("line")
      .data(target)
      .enter()
      .append("line")
      .attr("x1", 0)
      .attr("y1", (d) => this.tscale(d))
      .attr("x2", this.endX - (60 + (this.config.grid?.left || 0)))
      .attr("y2", (d) => this.tscale(d))
      .style("stroke", this.config.axies.temp.color || "#000")
      .style("stroke-width", 1)
      .style("stroke-dasharray", "2 10");
  }

  private addDepthAxis() {
    const g = this.zoom
      .append("g")
      .classed("depth-axis", true)
      .attr("transform", `translate(${this.startX}, 0)`);
    const domain = [this.config.axies.depth.start, this.config.axies.depth.end];
    this.yscale = d3
      .scaleLinear()
      .range([this.startY, this.endY])
      .domain(domain)
      .nice();

    const axis = d3.axisLeft(this.yscale).ticks(5);
    g.append("g").call(axis);
    g.append("text")
      .text(this.config.axies.depth.label)
      .attr("x", -55)
      .attr("y", (this.startY + this.endY) / 2)
      .style("fill", this.config.axies.depth.color || "#000")
      .style("font-size", this.config.axies.fontSize || 14)
      .style("font-weight", this.config.axies.fontWeight || "bold")
      .style("writing-mode", "tb")
      .style("text-anchor", "middle")
      .style("dominant-baseline", "middle");
  }

  private addYearAxis() {
    const g = this.zoom
      .append("g")
      .classed("year-axis", true)
      .attr("transform", `translate(0, ${this.endY})`);
    const domain = [this.config.axies.year.end, this.config.axies.year.start];
    this.xscale = d3
      .scaleLinear()
      .range([this.startX, this.endX])
      .domain(domain);

    g.append("g").call(d3.axisBottom(this.xscale));
    g.append("text")
      .text(this.config.axies.year.label)
      .attr("x", (this.startX + this.endX) / 2)
      .attr("y", 37)
      .style("fill", this.config.axies.year.color || "#000")
      .style("font-size", this.config.axies.fontSize || 14)
      .style("font-weight", this.config.axies.fontWeight || "bold")
      .style("text-anchor", "middle")
      .style("dominant-baseline", "middle");
  }

  addBlocks() {
    this.series = this.zoom.append("g").classed("series", true);
    const g = this.series.append("g").classed("frames", true);

    g.append("rect")
      .classed("upframe", true)
      .attr("x", this.startX)
      .attr("y", this.startY - this.block)
      .attr("width", this.endX - this.startX + this.block)
      .attr("height", this.block)
      .style("stroke", this.config.series.color || "#000")
      .style("fill", "none");

    g.append("rect")
      .classed("rtframe", true)
      .attr("x", this.endX)
      .attr("y", this.startY - this.block)
      .attr("width", this.block)
      .attr("height", this.endY - this.startY + this.block)
      .style("stroke", this.config.series.color || "#000")
      .style("fill", "none");

    this.addRtBlocks();
    this.addTopBlocks();
  }

  private addRtBlocks() {
    const rtblocks = this.series
      .append("g")
      .classed("rt-blocks", true)
      .selectAll("g")
      .data(this.config.series.levels)
      .enter()
      .append("g")
      .classed("block-item", true);

    rtblocks
      .append("rect")
      .attr("x", this.endX)
      .attr("y", (d) => this.yscale(d.depth.start))
      .attr("width", this.block)
      .attr(
        "height",
        (d) => this.yscale(d.depth.end) - this.yscale(d.depth.start)
      );

    rtblocks
      .append("text")
      .text((d) => d.name + "")
      .attr("x", this.endX + this.block / 2)
      .attr("y", (d) => {
        return (this.yscale(d.depth.end) + this.yscale(d.depth.start)) / 2;
      });
  }

  private addTopBlocks() {
    const topblocks = this.series
      .append("g")
      .classed("top-blocks", true)
      .selectAll("g")
      .data(this.config.series.levels)
      .enter()
      .append("g")
      .classed("block-item", true);

    topblocks
      .append("rect")
      .attr("x", (d) => this.xscale(d.year.end))
      .attr("y", this.startY - this.block)
      .attr("width", (d) => this.xscale(d.year.start) - this.xscale(d.year.end))
      .attr("height", this.block);

    topblocks
      .append("text")
      .text((d) => d.name + "")
      .attr(
        "x",
        (d) => (this.xscale(d.year.start) + this.xscale(d.year.end)) / 2
      )
      .attr("y", this.startY - this.block / 2);
  }

  private addSeryLines() {
    const line = d3
      .line()
      .curve(d3.curveMonotoneX)
      .x((d: any) => {
        return this.xscale(d.year);
      })
      .y((d: any) => this.tscale(d.temp));

    this.series
      .append("g")
      .classed("sery-lines", true)
      .selectAll("path")
      .data(this.config.series.levels)
      .enter()
      .append("path")
      .attr("d", (d) => line(d.data as any))
      .style("fill", (d) => {
        this.series
          .append("g")
          .classed("sery-points", true)
          .selectAll("circle")
          .data(d.data)
          .enter()
          .append("circle")
          .classed("circle-point", true)
          .attr("r", d.line?.point?.radius || 3)
          .attr("cx", (d) => this.xscale(d.year))
          .attr("cy", (d) => this.tscale(d.temp))
          .style("fill", d.line?.color || "#000");

        return "none";
      })
      .style("stroke", (d) => d.line?.color || "#000")
      .style("stroke-width", (d) => d.line?.width || 1);
  }
}
</script>
<style lang="scss" scoped>
.geo-chart {
  width: 100%;
  height: 100%;
  main {
    width: 100%;
    height: 100%;
  }
}
</style>
